#include "net/Socket.h"
#include "net/SockOpt.h"
#include "Base/Logger.h"
#include "Base/CurrectThread.h"
#include "Base/Buffer.h"
#include <cassert>
#include <unistd.h>
#include <errno.h>
#include <sys/uio.h>

namespace toym
{
namespace net
{

int Socket::bind(const InetAddress& addr) const {
    assert(m_sockfd >= 0);
    return SockOpt::bind(m_sockfd, addr.getAddr());
}

int Socket::listen(int nlisten) const {
    assert(m_sockfd >= 0);
    return SockOpt::listen(m_sockfd, nlisten);
}

int Socket::accept(InetAddress& peerAddr) const {
    assert(m_sockfd >= 0);
    struct sockaddr_in addr;
    int fd = SockOpt::accept4(m_sockfd, &addr);
    if (fd >= 0) {
        peerAddr.update(&addr);
    }
    return fd;
}

int Socket::connect(const InetAddress& addr) const {
    return SockOpt::connect(m_sockfd, addr.getAddr());
}

ssize_t Socket::read(Buffer& buf) const {
    char localBuf[64000];
    struct iovec iov[2];

    assert(m_sockfd >= 0);
    iov[0].iov_base = buf.getTail();
    iov[0].iov_len = buf.availableSize();
    iov[1].iov_base = localBuf;
    iov[1].iov_len = sizeof(localBuf);

    ssize_t nread = ::readv(m_sockfd, iov, sizeof(iov)/sizeof(struct iovec));
    ssize_t ret = nread;
    if (nread < 0) {
        CurrectThread::saveErrno(errno);
        LOG_ERROR << "Socket:: fd " << m_sockfd
                  << ", read raedv fail";
        CurrectThread::saveErrno(0);
    }
    if (nread > static_cast<ssize_t>(buf.availableSize())) {
        nread -= buf.availableSize();
        buf.adjustEnd(buf.availableSize());
        buf.append(localBuf, static_cast<std::size_t>(nread));
    } else {
        buf.adjustEnd(static_cast<std::size_t>(nread));
    }
    return ret;
}

ssize_t Socket::write(Buffer& buf) const {
    int nwrite = Socket::write(m_sockfd, buf.peek(), buf.size());
    if (nwrite > 0) {
        buf.read(static_cast<std::size_t>(nwrite));
    }
    return nwrite;
}

ssize_t Socket::write(const char* msg, std::size_t sz) const {
    return Socket::write(m_sockfd, msg, sz);
}

ssize_t Socket::write(const char* msg) const {
    return this->write(msg, strlen(msg));
}

ssize_t Socket::write(const std::string& msg) const {
    return this->write(msg.c_str(), msg.size());
}

void Socket::shutdownWrite() const {
    assert(m_sockfd >= 0);
    SockOpt::shutdownWrite(m_sockfd);
}

void Socket::setTcpNoDelay(bool on) const {
    SockOpt::setTcpNoDelay(m_sockfd, on);
}

void Socket::setKeepAlive(bool on) const {
    SockOpt::setKeepAlive(m_sockfd, on);
}

int Socket::getSocketError() const {
    return SockOpt::getSocketError(m_sockfd);
}

void Socket::close() {
    if (m_sockfd >= 0) {
        int ret = ::close(m_sockfd);
        if (ret < 0) {
            CurrectThread::saveErrno(errno);
            LOG_ERROR << "Socket::close fd " << m_sockfd
                      << ", close fail";
            CurrectThread::saveErrno(0);
        }
        m_sockfd = -1;
    }
}

InetAddress Socket::getPeerAddr() const {
    assert(m_sockfd >= 0);
    
    InetAddress ret(0);
    struct sockaddr_in addr;
    int t = SockOpt::getPeerAddr(m_sockfd, &addr);
    if (t >= 0) {
        ret.update(&addr);
    }
    return ret;
}

InetAddress Socket::getLocalAddr() const {
    assert(m_sockfd >= 0);

    InetAddress ret(0);
    struct sockaddr_in addr;
    int t = SockOpt::getLocalAddr(m_sockfd, &addr);
    if (t >= 0)  {
        ret.update(&addr);
    }
    return ret;
}

int Socket::setReuse(bool reuse) const {
    return SockOpt::setSocketReuse(m_sockfd, reuse);
}

//////////////////////////////////////////////
/////   Static Methods
///////////////////////////////////////////////

int Socket::createSocket() {
    return SockOpt::createSocketUnblocked4();
}

int Socket::write(int sockfd, const char* msg, std::size_t sz) {
    assert(sockfd >= 0);

    auto nwrite = ::write(sockfd, msg, sz);
    if (nwrite < 0) {
        CurrectThread::saveErrno(errno);
        LOG_ERROR << "Socket::write fd " << sockfd
                  << ", write fail";
        CurrectThread::saveErrno(0);
    }
    return nwrite;
}

int Socket::read(int sockfd, char* buf, std::size_t sz) {
    int nread = ::read(sockfd, buf, sz);
    if (nread < 0) {
        CurrectThread::saveErrno(errno);
        LOG_ERROR << "Socket::read fd" << sockfd
                  << ", read fail";
        CurrectThread::saveErrno(0);
    }
    return nread;
}

void Socket::close(int sockfd) {
    if (sockfd >= 0) {
        int ret = ::close(sockfd);
        if (ret < 0) {
            CurrectThread::saveErrno(errno);
            LOG_ERROR << "Socket::close fd " << sockfd
                        << ", close fail";
            CurrectThread::saveErrno(0);
        }
        sockfd = -1;
    }
}

} // namespace net

} // namespace toym
